import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import AccountContext
import SearchUI
import ContactsPeerItem
import ItemListUI

private enum ChannelDiscussionGroupSearchContent: Equatable {
    case peer(EnginePeer)
    
    static func ==(lhs: ChannelDiscussionGroupSearchContent, rhs: ChannelDiscussionGroupSearchContent) -> Bool {
        switch lhs {
            case let .peer(lhsPeer):
                if case let .peer(rhsPeer) = rhs {
                    return lhsPeer == rhsPeer
                } else {
                    return false
                }
        }
    }
    
    var peerId: EnginePeer.Id {
        switch self {
            case let .peer(peer):
                return peer.id
        }
    }
}

private final class ChannelDiscussionGroupSearchInteraction {
    let peerSelected: (EnginePeer) -> Void
    
    init(peerSelected: @escaping (EnginePeer) -> Void) {
        self.peerSelected = peerSelected
    }
}

private struct ChannelDiscussionGroupSearchEntryId: Hashable {
    let peerId: EnginePeer.Id
}

private final class ChannelDiscussionGroupSearchEntry: Comparable, Identifiable {
    let index: Int
    let content: ChannelDiscussionGroupSearchContent
    
    init(index: Int, content: ChannelDiscussionGroupSearchContent) {
        self.index = index
        self.content = content
    }
    
    var stableId: ChannelDiscussionGroupSearchEntryId {
        return ChannelDiscussionGroupSearchEntryId(peerId: self.content.peerId)
    }
    
    static func ==(lhs: ChannelDiscussionGroupSearchEntry, rhs: ChannelDiscussionGroupSearchEntry) -> Bool {
        return lhs.index == rhs.index && lhs.content == rhs.content
    }
    
    static func <(lhs: ChannelDiscussionGroupSearchEntry, rhs: ChannelDiscussionGroupSearchEntry) -> Bool {
        return lhs.index < rhs.index
    }
    
    func item(context: AccountContext, presentationData: PresentationData, interaction: ChannelDiscussionGroupSearchInteraction) -> ListViewItem {
        switch self.content {
            case let .peer(peer):
                return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
                    interaction.peerSelected(peer)
                })
        }
    }
}

struct ChannelDiscussionGroupSearchContainerTransition {
    let deletions: [ListViewDeleteItem]
    let insertions: [ListViewInsertItem]
    let updates: [ListViewUpdateItem]
    let isSearching: Bool
}

private func channelDiscussionGroupSearchContainerPreparedRecentTransition(from fromEntries: [ChannelDiscussionGroupSearchEntry], to toEntries: [ChannelDiscussionGroupSearchEntry], isSearching: Bool, context: AccountContext, presentationData: PresentationData, interaction: ChannelDiscussionGroupSearchInteraction) -> ChannelDiscussionGroupSearchContainerTransition {
    let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
    
    let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
    let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
    let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
    
    return ChannelDiscussionGroupSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
}

private struct ChannelDiscussionGroupSearchContainerState: Equatable {
}

final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerContentNode {
    private let context: AccountContext
    private let openPeer: (EnginePeer) -> Void
    
    private let dimNode: ASDisplayNode
    private let listNode: ListView
    
    private var enqueuedTransitions: [(ChannelDiscussionGroupSearchContainerTransition, Bool)] = []
    private var hasValidLayout = false
    
    private let searchQuery = Promise<String?>()
    private let searchDisposable = MetaDisposable()
    
    private var presentationData: PresentationData
    private var presentationDataDisposable: Disposable?
    
    private let presentationDataPromise: Promise<PresentationData>
    
    public override var hasDim: Bool {
        return true
    }
    
    init(context: AccountContext, peers: [EnginePeer], openPeer: @escaping (EnginePeer) -> Void) {
        self.context = context
        self.openPeer = openPeer
        
        let presentationData = context.sharedContext.currentPresentationData.with { $0 }
        self.presentationData = presentationData
        self.presentationDataPromise = Promise(self.presentationData)
        
        self.dimNode = ASDisplayNode()
        self.listNode = ListView()
        self.listNode.accessibilityPageScrolledString = { row, count in
            return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
        }
        
        super.init()
        
        self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
        self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
        self.listNode.isHidden = true
        
        self.addSubnode(self.dimNode)
        self.addSubnode(self.listNode)
        
        /*let statePromise = ValuePromise(ChannelDiscussionGroupSearchContainerState(), ignoreRepeated: true)
        let stateValue = Atomic(value: ChannelDiscussionGroupSearchContainerState())
        let updateState: ((ChannelDiscussionGroupSearchContainerState) -> ChannelDiscussionGroupSearchContainerState) -> Void = { f in
            statePromise.set(stateValue.modify { f($0) })
        }*/
        
        let interaction = ChannelDiscussionGroupSearchInteraction(peerSelected: { peer in
            openPeer(peer)
        })
        
        var searchIndex: [EngineDataBuffer: [EnginePeer]] = [:]
        for peer in peers {
            for token in peer._asPeer().indexName.indexTokens {
                if searchIndex[token] == nil {
                    searchIndex[token] = []
                }
                searchIndex[token]!.append(peer)
            }
        }
        
        let foundItems = searchQuery.get()
        |> mapToSignal { query -> Signal<[ChannelDiscussionGroupSearchEntry]?, NoError> in
            guard let query = query, !query.isEmpty else {
                return .single(nil)
            }
            
            var entries: [ChannelDiscussionGroupSearchEntry] = []
            let searchQueryTokens = context.engine.peers.tokenizeSearchString(string: query.lowercased(), transliteration: .none)
            var filteredPeers: [EnginePeer] = []
            var existingPeers = Set<EnginePeer.Id>()
            for (key, values) in searchIndex {
                inner: for token in searchQueryTokens {
                    if token.isPrefix(to: key) {
                        for peer in values {
                            if !existingPeers.contains(peer.id) {
                                existingPeers.insert(peer.id)
                                filteredPeers.append(peer)
                            }
                        }
                        break inner
                    }
                }
            }
            for peer in filteredPeers {
                entries.append(ChannelDiscussionGroupSearchEntry(index: entries.count, content: .peer(peer)))
            }
            return .single(entries)
        }
        
        let previousSearchItems = Atomic<[ChannelDiscussionGroupSearchEntry]?>(value: nil)
        
        self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get())
        |> deliverOnMainQueue).start(next: { [weak self] entries, presentationData in
            if let strongSelf = self {
                let previousEntries = previousSearchItems.swap(entries)
                let firstTime = previousEntries == nil
                let transition = channelDiscussionGroupSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, context: context, presentationData: presentationData, interaction: interaction)
                strongSelf.enqueueTransition(transition, firstTime: firstTime)
            }
        }))
        
        self.presentationDataDisposable = (context.sharedContext.presentationData
        |> deliverOnMainQueue).start(next: { [weak self] presentationData in
            if let strongSelf = self {
                let previousTheme = strongSelf.presentationData.theme
                let previousStrings = strongSelf.presentationData.strings
                
                strongSelf.presentationData = presentationData
                
                if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
                    strongSelf.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
                }
            }
        })
        
        self.listNode.beganInteractiveDragging = { [weak self] _ in
            self?.dismissInput?()
        }
    }
    
    deinit {
        self.searchDisposable.dispose()
        self.presentationDataDisposable?.dispose()
    }
    
    override func didLoad() {
        super.didLoad()
        
        self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
    }
    
    private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
        self.listNode.backgroundColor = theme.chatList.backgroundColor
    }
    
    override func searchTextUpdated(text: String) {
        if text.isEmpty {
            self.searchQuery.set(.single(nil))
        } else {
            self.searchQuery.set(.single(text))
        }
    }
    
    private func enqueueTransition(_ transition: ChannelDiscussionGroupSearchContainerTransition, firstTime: Bool) {
        self.enqueuedTransitions.append((transition, firstTime))
        
        if self.hasValidLayout {
            while !self.enqueuedTransitions.isEmpty {
                self.dequeueTransition()
            }
        }
    }
    
    private func dequeueTransition() {
        if let (transition, _) = self.enqueuedTransitions.first {
            self.enqueuedTransitions.remove(at: 0)
            
            var options = ListViewDeleteAndInsertOptions()
            options.insert(.PreferSynchronousDrawing)
            options.insert(.PreferSynchronousResourceLoading)
            
            let isSearching = transition.isSearching
            self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
                self?.listNode.isHidden = !isSearching
                self?.dimNode.isHidden = isSearching
            })
        }
    }
    
    override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
        super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
        
        let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
        
        var insets = layout.insets(options: [.input])
        insets.top += navigationBarHeight
        insets.left += layout.safeInsets.left
        insets.right += layout.safeInsets.right
        
        self.dimNode.frame = CGRect(origin: CGPoint(), size: layout.size)
        self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
        self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
        
        if !hasValidLayout {
            hasValidLayout = true
            while !self.enqueuedTransitions.isEmpty {
                self.dequeueTransition()
            }
        }
    }
    
    override func scrollToTop() {
        self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
    }
    
    @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
        if case .ended = recognizer.state {
            self.cancel?()
        }
    }
}
